#ifndef GAMEENGINE_GEOMETRY_BOX3D_HPP
#define GAMEENGINE_GEOMETRY_BOX3D_HPP

#include <GameEngine/Geometry/Core/coords2d.h>
#include <GameEngine/Geometry/Surfaces/trgl3dfast.h>

#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

//=================================================================================
// Implementation of the Box3d class

//! Default constructor
/*!
 @note Class T should have a default constructor
 */
template <class T>
Box3d<T>::Box3d() : bottom_left_down_(0,0,0), top_right_up_(1,1,1) {}

//! Constructor from minimum and maximum values for the X, Y and Z axes
/*!
 @param x_min The minimum value for the X axis
 @param x_max The maximum value for the X axis
 @param y_min The minimum value for the Y axis
 @param y_max The maximum value for the Y axis
 @param z_min The minimum value for the Z axis
 @param z_max The maximum value for the Z axis
 */
template <class T>
Box3d<T>::Box3d(const T& x_min, const T& x_max, const T& y_min, const T& y_max, const T& z_min, const T& z_max) :
	bottom_left_down_(x_min, y_min, z_min), top_right_up_(x_max, y_max, z_max) {}

//! Constructor from a bottom-left-down and a top-right-up points
/*!
 @param bottom_left_down The bottom-left-down point of the box
 @param top_right_up The top-right-up point of the box
 @note Class T should have a copy constructor
 */
template <class T>
Box3d<T>::Box3d(const Point3d<T>& bottom_left_down, const Point3d<T>& top_right_up) :
	bottom_left_down_(bottom_left_down), top_right_up_(top_right_up) {}

//! Copy constructor
/*!
 @param rhs The box to copy
 @note Class T should have a copy constructor
 */
template <class T>
Box3d<T>::Box3d(const Box3d& rhs) : bottom_left_down_(rhs.bottom_left_down_), top_right_up_(rhs.top_right_up_) {}

//! Destructor
/*!
 @note The destructor of class T should be public
 */
template <class T>
Box3d<T>::~Box3d() {}

//! Assignment operator
/*!
 @param rhs The box to copy
 @note Class T should have an assignment operator
 */
template <class T>
Box3d<T>& Box3d<T>::operator=(const Box3d& rhs) {
	if ( &rhs != this ) {
		bottom_left_down_ = rhs.bottom_left_down_;
		top_right_up_ = rhs.top_right_up_;
	}
	return *this;
}

//! Accessor to the bottom-left-down point of the box (read only)
/*!
 @return The bottom-left-down point of the box
 */
template <class T>
Point3d<T> Box3d<T>::bottom_left_down() const {
	return bottom_left_down_;
}

//! Accessor to the bottom-right-down point of the box (read only)
/*!
 @return The bottom-right-down point of the box
 */
template <class T>
Point3d<T> Box3d<T>::bottom_right_down() const {
	return Point3d<T>(top_right_up_.x(),bottom_left_down_.y(),bottom_left_down_.z());
}

//! Accessor to the top-left-down point of the box (read only)
/*!
 @return The top-left-down point of the box
 */
template <class T>
Point3d<T> Box3d<T>::top_left_down() const {
	return Point3d<T>(bottom_left_down_.x(),top_right_up_.y(),bottom_left_down_.z());
}

//! Accessor to the top-right-down point of the box (read only)
/*!
 @return The top-right-down point of the box
 */
template <class T>
Point3d<T> Box3d<T>::top_right_down() const {
	return Point3d<T>(top_right_up_.x(),top_right_up_.y(),bottom_left_down_.z());
}

//! Accessor to the bottom-left-up point of the box (read only)
/*!
 @return The bottom-left-up point of the box
 */
template <class T>
Point3d<T> Box3d<T>::bottom_left_up() const {
	return Point3d<T>(bottom_left_down_.x(),bottom_left_down_.z(),top_right_up_.z());
}

//! Accessor to the bottom-right-up point of the box (read only)
/*!
 @return The bottom-right-up point of the box
 */
template <class T>
Point3d<T> Box3d<T>::bottom_right_up() const {
	return Point3d<T>(top_right_up_.x(),bottom_left_down_.y(),top_right_up_.z());
}

//! Accessor to the top-left-up point of the box (read only)
/*!
 @return The top-left-up point of the box
 */
template <class T>
Point3d<T> Box3d<T>::top_left_up() const {
	return Point3d<T>(bottom_left_down_.x(),top_right_up_.y(),top_right_up_.z());
}

//! Accessor to the top-right-up point of the box (read only)
/*!
 @return The top-right-up point of the box
 */
template <class T>
Point3d<T> Box3d<T>::top_right_up() const {
	return top_right_up_;
}

//! Accessor to the minimum value of the box for the X axis (read only)
/*!
 @return The minimum value of the box for the X axis
 */
template <class T>
T Box3d<T>::min_x() const {
	return bottom_left_down_.x();
}

//! Accessor to the maximum value of the box for the X axis (read only)
/*!
 @return The maximum value of the box for the X axis
 */
template <class T>
T Box3d<T>::max_x() const {
	return top_right_up_.x();
}

//! Accessor to the minimum value of the box for the Y axis (read only)
/*!
 @return The minimum value of the box for the Y axis
 */
template <class T>
T Box3d<T>::min_y() const {
	return bottom_left_down_.y();
}

//! Accessor to the maximum value of the box for the Y axis (read only)
/*!
 @return The maximum value of the box for the Y axis
 */
template <class T>
T Box3d<T>::max_y() const {
	return top_right_up_.y();
}

//! Accessor to the minimum value of the box for the Z axis (read only)
/*!
 @return The minimum value of the box for the Z axis
 */
template <class T>
T Box3d<T>::min_z() const {
	return bottom_left_down_.z();
}

//! Accessor to the maximum value of the box for the Z axis (read only)
/*!
 @return The maximum value of the box for the Z axis
 */
template <class T>
T Box3d<T>::max_z() const {
	return top_right_up_.z();
}

//! Compute the point along the box at given parametric coordinates
/*!
 @param coords The parametric coordinates along the box axes
 @return The point along the box at given parametric coordinates
 */
template <class T>
Point3d<T> Box3d<T>::point(const Coords3d<T>& coords) const {
	return Point3d<T>(
				bottom_left_down_.x() + coords.u()*(top_right_up_.x()-bottom_left_down_.x()),
				bottom_left_down_.y() + coords.v()*(top_right_up_.y()-bottom_left_down_.y()),
				bottom_left_down_.z() + coords.w()*(top_right_up_.z()-bottom_left_down_.z())
			);
}

//! Compute the location of the given point
/*!
 @return The given point
 */
template <class T>
Point3d<T> Box3d<T>::operator[](Box3D::Index index) const {
	if ( index == Box3D::First ) return bottom_left_down_;
	if ( index == Box3D::Second ) return top_left_down();
	if ( index == Box3D::Third ) return bottom_right_down();
	if ( index == Box3D::Fourth ) return top_right_down();
	if ( index == Box3D::Fifth ) return bottom_left_up();
	if ( index == Box3D::Sixth ) return top_left_up();
	if ( index == Box3D::Seventh ) return bottom_right_up();
	return top_right_up_;
}

//! Test wether a point is inside the box
/*!
 @param rhs The point to test for inclusion
 @param strict If set to true, a point located exactly on the perimeter of
			   the box is not considered to belong to the box
 @return True if the point is inside the box, false otherwise
 */
template <class T>
bool Box3d<T>::inside(const Point3d<T>& rhs, bool strict) const {
	if ( strict ) {
		// Test x values
		if ( rhs.x() <= bottom_left_down_.x() ) return false;
		if ( rhs.x() >= top_right_up_.x() ) return false;

		// Test y values
		if ( rhs.y() <= bottom_left_down_.y() ) return false;
		if ( rhs.y() >= top_right_up_.y() ) return false;

		// Test z values
		if ( rhs.z() <= bottom_left_down_.z() ) return false;
		if ( rhs.z() >= top_right_up_.z() ) return false;
	}
	else {
		// Test x values
		if ( rhs.x() < bottom_left_down_.x() ) return false;
		if ( rhs.x() > top_right_up_.x() ) return false;

		// Test y values
		if ( rhs.y() < bottom_left_down_.y() ) return false;
		if ( rhs.y() > top_right_up_.y() ) return false;

		// Test z values
		if ( rhs.z() < bottom_left_down_.z() ) return false;
		if ( rhs.z() > top_right_up_.z() ) return false;
	}

	// Point is in the box
	return true;
}

//! Test wether the two boxes do overlap
/*!
 @param rhs The box to test for overlap
 @return True if the boxes do overlap, false otherwise
 */
template <class T>
bool Box3d<T>::overlap(const Box3d<T>& rhs) const {
	// Test X inclusion
	{{
		bool x_inclusion = false;

		// Test if rhs intersects this box
		if ( ( rhs.bottom_left_down_.x() > bottom_left_down_.x() ) && ( rhs.bottom_left_down_.x() < top_right_up_.x() ) ) x_inclusion = true;
		if ( x_inclusion == false ) {
			if ( ( rhs.top_right_up_.x() > bottom_left_down_.x() ) && ( rhs.top_right_up_.x() < top_right_up_.x() ) ) x_inclusion = true;
		}

		// Test if this box intersects rhs
		if ( x_inclusion == false ) {
			if ( ( bottom_left_down_.x() > rhs.bottom_left_down_.x() ) && ( bottom_left_down_.x() < rhs.top_right_up_.x() ) ) x_inclusion = true;
		}
		if ( x_inclusion == false ) {
			if ( ( top_right_up_.x() > rhs.bottom_left_down_.x() ) && ( top_right_up_.x() < rhs.top_right_up_.x() ) ) x_inclusion = true;
		}

		// If no X inclusion, there is no inclusion
		if ( x_inclusion == false ) return false;
	}}

	// Test Y inclusion
	{{
		bool y_inclusion = false;

		// Test if rhs intersects this box
		if ( ( rhs.bottom_left_down_.y() > bottom_left_down_.y() ) && ( rhs.bottom_left_down_.y() < top_right_up_.y() ) ) y_inclusion = true;
		if ( y_inclusion == false ) {
			if ( ( rhs.top_right_up_.y() > bottom_left_down_.y() ) && ( rhs.top_right_up_.y() < top_right_up_.y() ) ) y_inclusion = true;
		}

		// Test if this box intersects rhs
		if ( y_inclusion == false ) {
			if ( ( bottom_left_down_.y() > rhs.bottom_left_down_.y() ) && ( bottom_left_down_.y() < rhs.top_right_up_.y() ) ) y_inclusion = true;
		}
		if ( y_inclusion == false ) {
			if ( ( top_right_up_.y() > rhs.bottom_left_down_.y() ) && ( top_right_up_.y() < rhs.top_right_up_.y() ) ) y_inclusion = true;
		}

		// If no Y inclusion, there is no inclusion
		if ( y_inclusion == false ) return false;
	}}

	// Test Z inclusion
	{{
		// Test if rhs intersects this box
		if ( ( rhs.bottom_left_down_.z() > bottom_left_down_.z() ) && ( rhs.bottom_left_down_.z() < top_right_up_.z() ) ) return true;
		if ( ( rhs.top_right_up_.z() > bottom_left_down_.z() ) && ( rhs.top_right_up_.z() < top_right_up_.z() ) ) return true;

		// Test if this box intersects rhs
		if ( ( bottom_left_down_.z() > rhs.bottom_left_down_.z() ) && ( bottom_left_down_.z() < rhs.top_right_up_.z() ) ) return true;
		if ( ( top_right_up_.z() > rhs.bottom_left_down_.z() ) && ( top_right_up_.z() < rhs.top_right_up_.z() ) ) return true;
	}}

	// No intersection
	return false;
}


#ifdef TODOTODOTODO
//! Compute the intersection between the line and the box
/*!
 @param rhs The line to intersect with the box
 @param line_coordinate1 The parametric line coordinate of the first intersection between
                         the box and the line, if both do intersect. To retrieve the
					     intersection point, call rhs.point(line_coordinate1)
 @param line_coordinate2 The parametric line coordinate of the second intersection between
                         the box and the line, if both do intersect. To retrieve the
					     intersection point, call rhs.point(line_coordinate2)
 @param type If not nil, this parameter receives the type of intersection
             between the box and the line. The intersection type can be:<BR>
			 NoIntersection if the line does not intersect the box,<BR>
			 Intersection if the line intersects the box
 @return The number of intersections between the line and the box
 */
template <class T>
int Box3d<T>::intersection( const Line3d<T>& rhs, T& line_coordinate1, T& line_coordinate2, IntersectionType::Type* type) const
{
	int res = 0;

	// Get the four box points
	const Point2d<T>& p1 = bottom_left_;
	Point2d<T> p2 = bottom_right();
	const Point2d<T>& p3 = top_right_;
	Point2d<T> p4 = top_left();

	// Test the first line
	T tmp_coord, tmp_coord2;
	IntersectionType::Type tmp_type;
	{{
		Line2d<T> line(p1, p2);
		tmp_type = line.intersection(rhs, tmp_coord, &tmp_coord2);
		if ( tmp_type == IntersectionType::Intersection ) {
			if ( ( tmp_coord >= 0 ) && ( tmp_coord <= 1 ) ) {
				line_coordinate1 = tmp_coord2;
				++res;
			}
		}
	}}

	// Test the second line
	{{
		Line2d<T> line(p2, p3);
		tmp_type = line.intersection(rhs, tmp_coord, &tmp_coord2);
		if ( tmp_type == IntersectionType::Intersection ) {
			if ( ( tmp_coord >= 0 ) && ( tmp_coord <= 1 ) ) {
				if ( res == 0 ) line_coordinate1 = tmp_coord2;
				else line_coordinate2 = tmp_coord2;
				++res;
			}
		}
	}}

	// Test the third line
	{{
		if ( res < 2 ) {
			Line2d<T> line(p3, p4);
			tmp_type = line.intersection(rhs, tmp_coord, &tmp_coord2);
			if ( tmp_type == IntersectionType::Intersection ) {
				if ( ( tmp_coord >= 0 ) && ( tmp_coord <= 1 ) ) {
					if ( res == 0 ) line_coordinate1 = tmp_coord2;
					else line_coordinate2 = tmp_coord2;
					++res;
				}
			}
		}
	}}

	// Test the fourth line
	{{
		if ( res < 2 ) {
			Line2d<T> line(p4, p1);
			tmp_type = line.intersection(rhs, tmp_coord, &tmp_coord2);
			if ( tmp_type == IntersectionType::Intersection ) {
				if ( ( tmp_coord >= 0 ) && ( tmp_coord <= 1 ) ) {
					if ( res == 0 ) line_coordinate1 = tmp_coord2;
					else line_coordinate2 = tmp_coord2;
					++res;
				}
			}
		}
	}}

	if ( type ) *type = ( ( res > 0 ) ? IntersectionType::Intersection : IntersectionType::NoIntersection );
	return res;
}
#endif

//! Compute the parametric coordinates of the closest point along the box
/*!
 @param rhs The point to project on the box
 @param coords Receives the parametric coordinates of the closest point along the box
 @return True if parametric coordinates of the closest point could be computed (this function
         should return false only if the box is degenerate)
 @note If you want to get back to the point location, call the point(coords) function
 */
template <class T>
bool Box3d<T>::closest_point(const Point3d<T>& rhs, Coords3d<T>& coords) const {
	// Test ranges
	bool x_range = ( ( rhs.x() >= bottom_left_down_.x() ) && ( rhs.x() <= top_right_up_.x() ) );
	bool y_range = ( ( rhs.y() >= bottom_left_down_.y() ) && ( rhs.y() <= top_right_up_.y() ) );
	bool z_range = ( ( rhs.z() >= bottom_left_down_.z() ) && ( rhs.z() <= top_right_up_.z() ) );

	// Get the closest side
	T x_side = rhs.x() - ( bottom_left_down_.x() + top_right_up_.x() ) / (T)2;
	T y_side = rhs.y() - ( bottom_left_down_.y() + top_right_up_.y() ) / (T)2;
	T z_side = rhs.z() - ( bottom_left_down_.z() + top_right_up_.z() ) / (T)2;

	// Get normalized coordinates
	T x = ( rhs.x() - bottom_left_down_.x() ) / ( top_right_up_.x() - bottom_left_down_.x() );
	T y = ( rhs.y() - bottom_left_down_.y() ) / ( top_right_up_.y() - bottom_left_down_.y() );
	T z = ( rhs.z() - bottom_left_down_.z() ) / ( top_right_up_.z() - bottom_left_down_.z() );

	// Point is in the box
	if ( ( x_range == true ) && ( y_range == true ) && ( z_range == true ) ) {
		if ( ( abs(x_side) > abs(y_side) ) && ( abs(x_side) > abs(z_side) ) ) {
			if ( x_side < 0 ) coords = Coords3d<T>((T)0,y,z);
			else coords = Coords3d<T>((T)1,y,z);
		}
		else if ( ( abs(y_side) > abs(x_side) ) && ( abs(y_side) > abs(z_side) ) ) {
			if ( y_side < 0 ) coords = Coords3d<T>(x,(T)0,z);
			else coords = Coords3d<T>(x,(T)1,z);
		}
		else {
			if ( z_side < 0 ) coords = Coords3d<T>(x,y,(T)0);
			else coords = Coords3d<T>(x,y,(T)1);
		}
		return true;
	}

	// Test X range
	if ( x_range == true ) {
		coords.u() = x;
	}
	else {
		if ( x_side < 0 ) coords.u() = (T)0;
		else coords.u() = (T)1;
	}

	// Test Y range
	if ( y_range == true ) {
		coords.v() = y;
	}
	else {
		if ( y_side < 0 ) coords.v() = (T)0;
		else coords.v() = (T)1;
	}

	// Test Z range
	if ( z_range == true ) {
		coords.w() = z;
	}
	else {
		if ( z_side < 0 ) coords.w() = (T)0;
		else coords.w() = (T)1;
	}

	// Return success
	return true;
}

//! Compute the closest point along the box
/*!
 @return The closest point along the box
 @param rhs The point to project on the box
 @note This is an overloaded function, provided for convenience and for compatibility
       with some template classes. It is slower than the function
	   closest_point(const Point3d<T>& rhs, Coords3d<T>& coords). Unless you explicitly
	   need the point location, its use is therefore not advised
 */
template <class T>
Point3d<T> Box3d<T>::closest_point(const Point3d<T>& rhs) const {
	// Get the point coordinates
	Coords3d<T> coordinates;
	closest_point(rhs, coordinates);

	// Return the point location
	return point(coordinates);
}

//! Compute the perimeter of the box
/*!
 @return The perimeter of the box
 */
template <class T>
T Box3d<T>::area() const {
	return (T)2*(top_right_up_.x()-bottom_left_down_.x())*(top_right_up_.y()-bottom_left_down_.y())
		 + (T)2*(top_right_up_.x()-bottom_left_down_.x())*(top_right_up_.z()-bottom_left_down_.z())
		 + (T)2*(top_right_up_.y()-bottom_left_down_.y())*(top_right_up_.z()-bottom_left_down_.z());
}

//! Compute the area of the box
/*!
 @return The area of the box
 */
template <class T>
T Box3d<T>::volume() const {
	return (top_right_up_.x()-bottom_left_down_.x())*(top_right_up_.y()-bottom_left_down_.y())*(top_right_up_.z()-bottom_left_down_.z());
}

//! Check if the box is degenerate
/*!
 @return True if the box is degenerate, false otherwise
 @note The box is considered degenerate if its area is equal
       or below the tolerance threshold
 */
template <class T>
bool Box3d<T>::degenerate(const T& tolerance) const {
	return ( volume() <= tolerance );
}

//! Check if the coordinates are in the box
/*!
 @param coords The parametric coordinates to test
 @param strict If set to true, points located on the edges of the box
               are not considered to belong to the box
 @return True if the coordinates are in the box, false otherwise
 */
template <class T>
bool Box3d<T>::in_box(const Coords3d<T>& coords, bool strict) {
	if ( strict ) return ( ( coords.u() >  0 ) && ( coords.v() >  0 ) && ( coords.w() >  0 ) && ( coords.u() <  1 ) && ( coords.v() <  1 ) && ( coords.w() <  1 ) );
	/* else */    return ( ( coords.u() >= 0 ) && ( coords.v() >= 0 ) && ( coords.w() >= 0 ) && ( coords.u() <= 1 ) && ( coords.v() <= 1 ) && ( coords.w() <= 1 ) );
}

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif

#endif
